﻿@model Documentation.Models.DocModel

@{
    Layout = null;
}

<!DOCTYPE html>

<script type="text/javascript" src="~/Scripts/syntaxhighlighter/shCore.js"></script>
<script type="text/javascript" src="~/Scripts/syntaxhighlighter/shBrushCSharp.js"></script>

<link href="~/Content/syntaxhighlighter/shCore.css" rel="stylesheet" type="text/css" />
<link href="~/Content/syntaxhighlighter/shThemeDefault.css" rel="stylesheet" type="text/css" />

<html>
    <head>
        @{
            var title = "Query Lifting – safe textual queries";
        }
        <title>@title</title>
        <link rel="stylesheet" href="~/Content/Site.css" type="text/css" />
    </head>
    <body>
        <h1>@title</h1>
        
        @*//TODO AddWithValue для Nullable<T> наверное не работает. Перевести весь код на p.Value *@
        @*//TODO Правильно организовать ссылки в этом документе *@
        @*//TODO Проверить орфографию *@
        @*//TODO Разместить статью на внешнем ASP.NET хостинге. Подумать о поисковиках, они пока не нужны. *@
        @*//TODO Разобраться с Git. В итоге поместить проект в github. *@
        @*//TODO Разобраться с Nuget. Сделать nuget проект для CQ. *@

        <p>
            С одной стороны, определение запроса в виде строки является мощным и гибким средством для работы
            с базами данных, с другой стороны, такой код сложно писать, развивать и сопровождать, 
            по такому коду нет навигации. Для смягчения указанных недостатков код с текстовыми запросами
            покрывают unit тестами. Написание таких unit тестов это ручной рутинный труд.
            Человек может допустить ошибки при написании тестов.
            Нет гарантий, что тесты будут написаны на все варианты запросов.
        </p>
        <p>
            Возникают естественные вопросы. Почему рутинный труд не поручить машине?
            Для чего при решении этой задачи требуется человеческий мозг?
            Для ответов на эти вопросы разберем детали интеграции текстовых запросов
            и кода на языке общего назначения.
        </p>
        <p>
            Рассмотрим пример.
        </p>
        <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
        private static void Example1(DateTime? date)
        {
            using (var connection = new SqlConnection(ConnectionString))
            {
                connection.Open();
                using (var command = connection.CreateCommand())
                {
                    var builder = new StringBuilder(@@"
SELECT  Id, CreationDate
FROM    Post
WHERE   1 = 1");
                    if (date.HasValue)
                    {
                        builder.Append(@@"
        AND CreationDate >= @@Date");
                        command.Parameters.AddWithValue("@@Date", date);
                    }
                    command.CommandText = builder.ToString();
                    using (var reader = command.ExecuteReader())
                    {
                        var idOrdinal = reader.GetOrdinal("Id");
                        var creationDateOrdinal = reader.GetOrdinal("CreationDate");
                        while (reader.Read())
                        {
                            var id = reader.GetInt32(idOrdinal);
                            var creationDate = reader.GetDateTime(creationDateOrdinal);
                        }
                    }
                }
            }
        }
        ]]>
    </script>
    <h2>1. Тип для результата</h2>
    <p>
        Пары [id, Int32] и [CreationDate, DateTime] можно представить в виде интерфейса.
    </p>
        <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
public interface IPostInfo
{
    int Id { get; }
    DateTime CreationDate { get; }
}
        ]]>
    </script>
    <p>
        Тогда, используя runtime кодогенерацию
        (см. <a href="http://msdn.microsoft.com/en-us/library/system.reflection.emit.aspx">Emit</a>),
        строчки 21-27 можно переписать следующим образом.
    </p>
        <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
        private static void Example1(DateTime? date)
        {
            using (var connection = new SqlConnection(ConnectionString))
            {
                connection.Open();
                using (var command = connection.CreateCommand())
                {
                    var builder = new StringBuilder(@@"
SELECT  Id, CreationDate
FROM    Post
WHERE   1 = 1");
                    if (date.HasValue)
                    {
                        builder.Append(@@"
        AND CreationDate >= @@Date");
                        command.Parameters.AddWithValue("@@Date", date);
                    }
                    command.CommandText = builder.ToString();
                    using (var reader = command.ExecuteReader())
                    {
                        var ordinals = reader.GetOrdinals<IPostInfo>();
                        while (reader.Read())
                        {
                            IPostInfo postInfo = ordinals.Materialize(reader);
                        }
                    }
                }
            }
        }
        ]]>
    </script>
    <h2 id="sec2">2. Тестирование посредством SQL метаданных</h2>
    <p>
        Сделаем преобразование кода, извлечем метод <code>Example1Query</code>
        (<a href="http://www.jetbrains.com/resharper/webhelp50/Refactorings__Extract_Method.html">Extract Method</a>).
    </p>
    <p id="ref1">Листинг 1.</p>
    <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
        private static void Example1(DateTime? date)
        {
            using (var connection = new SqlConnection(ConnectionString))
            {
                connection.Open();
                using (var command = connection.CreateCommand())
                {
                    var prototype = Example1Query(date, command);
                    using (var reader = command.ExecuteReader())
                    {
                        var ordinals = reader.GetOrdinals(prototype);
                        while (reader.Read())
                        {
                            IPostInfo postInfo = ordinals.Materialize(reader);
                        }
                    }
                }
            }
        }

        private static IPostInfo Example1Query(DateTime? date, SqlCommand command)
        {
            var builder = new StringBuilder(@@"
SELECT  Id, CreationDate
FROM    Post
WHERE   1 = 1");
            if (date.HasValue)
            {
                builder.Append(@@"
        AND CreationDate >= @@Date");
                command.Parameters.AddWithValue("@@Date", date);
            }
            command.CommandText = builder.ToString();
            return default(IPostInfo);
        }
        ]]>
    </script>
    <p>
        Для метода <code>Example1Query</code> можно написать unit тест,
        который выполнит SQL запрос в режиме 
        <a href="http://msdn.microsoft.com/en-us/library/system.data.commandbehavior.aspx"
           ><code>SchemaOnly</code></a>.
        ADO.NET предоставляет метод
        <a href="http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldatareader.getschematable(v=vs.110).aspx"
           ><code>SqlDataReader.GetSchemaTable</code></a>, который возвращает метаданные колонок.
        В тесте ниже сравниваются метаданные колонок со свойствами интерфейса <code>IPostInfo</code>.
    </p>
        <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
        [TestMethod]
        public void Example1Test()
        {
            MetadataTest(command => Program.Example1Query(null, command));
            MetadataTest(command => Program.Example1Query(new DateTime(2000, 1, 1), command));
        }

        private static void MetadataTest<T>(Func<SqlCommand, T> func)
        {
            using (var connection = new SqlConnection(Program.ConnectionString))
            {
                connection.Open();
                using (var command = connection.CreateCommand())
                {
                    func(command);
                    using (var reader = command.ExecuteReader(CommandBehavior.SchemaOnly))
                    {
                        var expected = reader.GetSchemaTable().Rows.Cast<DataRow>().Select(row => new {
                            Name = (string) row["ColumnName"],
                            Type = GetType(row)
                        });
                        var actual = typeof (T).GetProperties().Select(info => new {
                            info.Name,
                            Type = info.PropertyType
                        });
                        if (expected.Except(actual).Any() || actual.Except(expected).Any())
                            throw new ApplicationException(@@"Expected type:
public interface " + typeof(T).Name + @@"
{
" + string.Join(@@"
", expected.Select(_ => @@"    " + _.Type.ToCSharp() + @@" " + _.Name + @@" { get; }")) + @@"
}");
                    }
                }
            }
        }

        private static Type GetType(DataRow row)
        {
            var type = (Type) row["DataType"];
            return type.IsClass || false.Equals(row["AllowDBNull"])
                ? type
                : typeof (Nullable<>).MakeGenericType(type);
        }
        ]]>
    </script>
    <p>
        Если метаданные колонок не совпадут с интерфейсом <code>IPostInfo</code>,
        то в тексте сообщения будет указан полный текст ожидаемого интерфейса.
        Таким образом, текст интерфейса <code>IPostInfo</code>
        генерируется автоматически на основе текста SQL запроса.
    </p>
    <h2 id="sec3">3. Тестовые значения параметров</h2>
    <p>
        В тесте в параметр <code>date</code> метода <code>Example1Query</code>
        передаются значения <code>null</code> и <code>new DateTime(2000, 1, 1)</code>.
        Для теста важно лишь <code>date.HasValue</code>, а конкретное значение
        <code>date.Value</code> не используется в тесте. В режиме
        <a href="http://msdn.microsoft.com/en-us/library/system.data.commandbehavior.aspx"
        ><code>SchemaOnly</code></a> для выполнения запроса значения параметров не используются.
    </p>
    <p>
        Введем правила для типов параметров. Например, для параметров типа <code>DateTime?</code>
        подставляем значения <code>null</code> и <code>new DateTime(2000, 1, 1)</code>.
        Для типа <code>bool</code> – <code>true</code>, <code>false</code>.
        Для <code>enum</code> типов – все члены <code>enum</code>.
        Для типа <code>string</code> – <code>null</code> и <code>"test string"</code>.
        Для типа <code>Func&lt;T&gt;</code> – <code>() =&gt; TValue</code>, где
        <code>TValue</code> принимает значения из множества значений для типа T.
        Аналогично для типов <a href="http://msdn.microsoft.com/en-us/library/dd233245.aspx"
            ><code>Option</code></a> и <a href="http://msdn.microsoft.com/en-us/library/ee353439.aspx"
            ><code>Choice</code></a>.
        И т.д.
        Тогда тест будет состоять из одной строчки.
    </p>
    <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
        [TestMethod]
        public void Example1Test()
        {
            MetadataTest(typeof(Program), "Example1Query");
        }
        ]]>
    </script>
    <p>
        Если у тестируемого метода (<code>Example1Query</code>) будет несколько
        параметров, метод <code>MetadataTest</code> переберет все комбинации значений параметров.
    </p>
    <h2 id="sec4">4. Автоматический поиск методов для тестирования</h2>
    <p>
        Для полной автоматизации осталось избавиться в тесте от указания метода 
        <code>Example1Query</code>. Введем правило. Текст запроса всегда выставляется
        посредством вызова сеттера
        <a href="http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.commandtext(v=vs.110).aspx"
           ><code>SqlCommand.CommandText</code></a>.
        С помощью <a href="http://www.codeproject.com/Articles/14058/Parsing-the-IL-of-a-Method-Body"
        >анализа IL кода</a> можно найти все методы, в теле которых вызывается
        сеттер <a href="http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.commandtext(v=vs.110).aspx"
           ><code>SqlCommand.CommandText</code></a>.
        Теперь тест выглядит следующим образом.
    </p>
    <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
        [TestMethod]
        public void CheckAllQueries()
        {
            QueryChecker.CheckAllQueries(ConnectionString, GetType().Assembly.GetTypes());
        }
        ]]>
    </script>
    <p>
        Тест находит все методы, в теле которых вызывается сеттер
        <a href="http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.commandtext(v=vs.110).aspx"
           ><code>SqlCommand.CommandText</code></a> и
        для каждого найденного метода вызывает метод <code>MetadataTest</code>.
    </p>
    <h2 id="sec5">5. Код запроса внутри лямбда выражения</h2>
    <p>
        Теперь методы ищутся автоматически по IL коду, поэтому
        в C# коде метод с запросом может быть представлен в виде метода без имени – 
        лямбда выражения. Введем метод <code>Apply</code>.
    </p>
    <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
public static TResult Apply<T, TResult>(this T arg, Func<T, TResult> func)
{
    return func(arg);
}        
        ]]>
    </script>
    <p>
        Тогда <a href="#ref1">Листинг 1.</a> можно переписать следующим образом.
    </p>
    <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
        private static void Example1(DateTime? date)
        {
            using (var connection = new SqlConnection(ConnectionString))
            {
                connection.Open();
                using (var command = connection.CreateCommand())
                {
                    var prototype = new {date, command}.Apply(p => {
                        var builder = new StringBuilder(@@"
SELECT  Id, CreationDate
FROM    Post
WHERE   1 = 1");
                        if (p.date.HasValue)
                        {
                            builder.Append(@@"
        AND CreationDate >= @@Date");
                            p.command.Parameters.AddWithValue("@@Date", p.date);
                        }
                        p.command.CommandText = builder.ToString();
                        return default(IPostInfo);
                    });
                    using (var reader = command.ExecuteReader())
                    {
                        var ordinals = reader.GetOrdinals(prototype);
                        while (reader.Read())
                        {
                            IPostInfo postInfo = ordinals.Materialize(reader);
                        }
                    }
                }
            }
        }
        ]]>
    </script>
    <p>
        Если нет замыканий, то лямбда выражение компилируется в статический метод,
        поэтому метод <code>CheckAllQueries</code> может его вызвать и протестировать.
        Если замыкания есть, метод <code>CheckAllQueries</code> выдаст сообщение о невозможности
        автоматически протестировать этот запрос.
    </p>
    <h2 id="sec6">6. Helper Methods</h2>
    <p>
        Для удобства введем следующие методы.
    </p>
    <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
public static IEnumerable<T> List<T>(this Func<SqlCommand, T> it)
{
    return it.Result().ToList();
}

public static IEnumerable<T> Result<T>(this Func<SqlCommand, T> it)
{
    using (var connection = new SqlConnection(Program.ConnectionString))
    {
        connection.Open();
        using (var command = connection.CreateCommand())
        {
            var prototype = it(command);
            using (var reader = command.ExecuteReader())
            {
                var ordinals = reader.GetOrdinals(prototype);
                while (reader.Read())
                    yield return ordinals.Materialize(reader);
            }
        }
    }            
}
        ]]>
    </script>
    <p>
        Тогда наш пример записывается следующим образом.
    </p>
    <p id="ref1_1">Листинг 1.1.</p>
    <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
        private static void Example1(DateTime? date)
        {
            var postInfos = new {date}.Apply((с, p) => {
                var builder = new StringBuilder(@@"
SELECT  Id, CreationDate
FROM    Post
WHERE   1 = 1");
                if (p.date.HasValue)
                {
                    builder.Append(@@"
        AND CreationDate >= @@Date");
                    с.Parameters.AddWithValue("@@Date", p.date);
                }
                с.CommandText = builder.ToString();
                return default(IPostInfo);
            }).List();
        }
        ]]>
    </script>
    <h2 id="sec7">7. Подведем итоги: за чем следит человек, а за чем машина?</h2>
    <p>
        У нас получилась следующая схема для автоматического тестирования текстовых SQL запросов.
        Человек должен использовать сеттер
        <a href="http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.commandtext(v=vs.110).aspx"
           ><code>CommandText</code></a> для задания текста SQL запроса.
        После этого машина начинает отслеживать выполнение нижеследующих правил.
        Если правила не выполняются, машина выдает сообщение о невозможности выполнить
        автоматическое тестирвоание SQL запроса.
    <p>
    </p>
        Метод, содержащий <a href="http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.commandtext(v=vs.110).aspx"
           ><code>CommandText</code></a>, должен быть статическим.
        Тип возвращаемого значения у метода должен полностью совпадать с метаданными,
        которые возвращает метод <a href="http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldatareader.getschematable(v=vs.110).aspx"
           ><code>GetSchemaTable</code></a>.
        Параметры метода должны иметь типы, для которых заданы множества тестовых значений,
        см. раздел 3.
    <p>
    </p>
        Достаточно ли тестирования на заданных множествах значений?
        Контроль полноты тестирования можно оставить на стороне человека.
        Обычно для unit тестов так и поступают.
    </p>
    <h2 id="sec8">8. Формальная процедура для облегчение контроля полноты тестирования</h2>
    <p>
        Для облегчения контроля полноты тестирования можно воспользоваться следующей формальной
        процедурой. Если значение параметра не участвует в формировании запроса,
        а передается в коллекцию SQL параметров, то такие параметры будем называть транзитивными.
        В метод с запросом транзитивные параметры должны передаваться завернутыми в 
        специальную структуру <code>Param&lt;T&gt;</code>.
    </p>
    <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
public struct Param<T>
{
    private readonly T value;

    public Param(T value)
    {
        this.value = value;
    }

    public void AddTo(SqlCommand command, string parameterName)
    {
        ...
    }
}        
        ]]>
    </script>
    <p>
        Снаружи значение <code>value</code> не доступно.
        Это гарантирует, что внутри метода с запросом значение не используется.
        Оно добавляется в коллекцию SQL параметров посредством метода <code>AddTo</code>.
    </p>
    <p>
        Для нашего примера описанная процедура выглядит следующим образом.
    </p>
    <p id="ref2">Листинг 2.</p>
    <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
        private static void Example1(DateTime? date)
        {
            var postInfos = new {date = date.Select(_ => _.Param())}.Apply((с, p) => {
                var builder = new StringBuilder(@@"
SELECT  Id, CreationDate
FROM    Post
WHERE   1 = 1");
                if (p.date.HasValue)
                {
                    builder.Append(@@"
        AND CreationDate >= @@Date");
                    p.date.Value.AddTo(с, "@@Date");
                }
                с.CommandText = builder.ToString();
                return default(IPostInfo);
            }).List();
        }
        ]]>
    </script>
    <p>где</p>
    <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
        public static Param<T> Param<T>(this T it)
        {
            return new Param<T>(it);
        }

        public static TResult? Select<T, TResult>(this T? it, Func<T, TResult> selector) 
            where T: struct where TResult: struct 
        {
            if (it.HasValue)
                return selector(it.Value);
            else
                return null;
        }
        ]]>
    </script>
    <h2 id="sec9">9. Краткий синтаксис посредством Razor</h2>
    <p>
        Для смешивания C# кода и текста удобно воспользоваться
        <a href="http://weblogs.asp.net/scottgu/archive/2010/07/02/introducing-razor.aspx">Razor</a>.
        <a href="http://vibrantcode.com/blog/2010/11/16/hosting-razor-outside-of-aspnet-revised-for-mvc3-rc.html"
           >Razor может хоститься вне ASP.NET.</a>. По сути реализация Razor – это набор классов,
        который превращает текст шаблона в дерево
        <a href="http://msdn.microsoft.com/en-us/library/y2k85ax6(v=vs.110).aspx">CodeDOM</a>.
        Затем дерево CodeDOM компилируется в исполняемый код.
    </p>
    <p>
        Наш пример (<a href="#ref2">Листинг 2</a>) при использовании Razor выглядит
        следующим образом.
    </p>
    <p id="ref3">Листинг 3.</p>
    <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
        private static void Example1(DateTime? date)
        {
            var posts = Query<IPostInfo>.New(new {date}, @@"
SELECT  Id, CreationDate
FROM    Post
WHERE   1 = 1
@@if (date.HasValue) {
        @@:AND CreationDate >= @@date
}").List();
        }
        ]]>
    </script>
    <h3 id="sec9_1">9.1 Database parameter inference</h3>
    <p>
        Как видно из <a href="#ref3">Листинг 3</a>, при использовании Razor не требуется
        писать <code>date.Select(_ => _.Param())</code>.
        При передаче параметров в Razor-шаблон происходит вывод (inference)
        транзитивных параметров.
    </p>
    <p>
        Database parameter inference хорошо сочетается с такими типами как
        <a href="http://msdn.microsoft.com/en-us/library/dd233245.aspx"
            ><code>Option</code></a> и <a href="http://msdn.microsoft.com/en-us/library/ee353439.aspx"
            ><code>Choice</code></a>. Например, для текстового поля поиска
        вызов функции <a href="http://msdn.microsoft.com/en-us/library/system.string.isnullorempty.aspx"
            ><code>string.IsNullOrEmpty</code></a> проще оставить снаружи запроса,
        тогда для типа <code>Option&lt;T&gt;</code> сработает database parameter inference.
    </p>
    <p id="ref4">Листинг 4.</p>
    <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
            Query<IPostInfo>.New(new {search = textBox.Text.None(string.IsNullOrEmpty)}, @@"
...
WHERE   1 = 1
@@if (search.HasValue) {
        @@:AND (SourceTagName LIKE @@search OR TargetTagName LIKE @@search)
}")
        }
        ]]>
    </script>
    где
    <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
        public static Option<T> None<T>(this T it, Func<T, bool> predicate)
        {
            return predicate(it) ? new Option<T>() : it;
        }
        ]]>
    </script>
    <h3>9.2 Добавление SQL параметров в коллекцию</h3>
    <p>
        Фрагмент Razor-шаблона <code>@@search</code> (<a href="#ref4">Листинг 4</a>)
        преобразуется Razor движком в вызов метода:
    </p>
    <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
    public class QWriter
    {
        public QWriter Write<T>(Option<Param<T>> arg)
        {
            ...
        }
    }
        ]]>
    </script>
    <p>
        В строчке 5 в <a href="#ref4">Листинг 4</a> метод <code>Write</code>
        будет вызван два раза. Желательно, чтобы параметер <code>search</code>
        добавился в коллекцию SQL параметров один раз. Поэтому требуется где-то
        сохранять признак того, что параметр уже был добавлен в коллекцию.
        Сделаем такой признак в типе <code>Param&lt;T&gt;</code>.
        Есть второй вариант – завести <code>Dictionary</code> внутри
        <code>QWriter</code>. Использование <code>Dictionary</code>
        немного снизит производительность, поэтому выбран первый вариант.
        При таком выборе один и тот же экземпляр типа <code>Param&lt;T&gt;</code>
        не может быть добавлен в несколько коллекций.
    </p>
    <h2 id="sec10">10. Find Usages для сущностей базы данных: таблиц, колонок и т.д.</h2>
    <p>
        Метод <code>CheckAllQueries</code> перебирает все варианты запросов.
        Временно создав хранимую процедуру на основе текста запроса,
        с помощью процедуры <a href="http://technet.microsoft.com/en-us/library/bb677185.aspx"
            ><code>sys.dm_sql_referenced_entities</code></a>
        можно узнать какие сущности базы данных используются в запросе.
        Такой алгоритм используюется в методе <code>FindUsages</code>.
    </p>
    <p>
        Пример использования метода <code>FindUsages</code>.
    </p>
    <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
FindUsages(tableName: "Post", columnName: "CreationDate")
        ]]>
    </script>
    <p>
        Возвращаемый результат.
    </p>
    <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
C:\...\Source\ControllableQuery.Demo\SomeClass1.cs (Ln 15)
C:\...\Source\ControllableQuery.Demo\SomeClass2.cs (Ln 27)
C:\...\Source\ControllableQuery.Demo\SomeClass2.cs (Ln 103)
        ]]>
    </script>
    <p>
        Указаны файл, номер строки и номер столбца в файле, которые указывают на начало метода.
        Эти методы генерируют запросы, в которых используется
        колонка <code>Post.CreationDate</code>.
    </p>
    <h2>11. INSERT, UPDATE, DELETE</h2>
    <p>
        Примеры выполнение операций <code>INSERT</code> и <code>UPDATE</code>.
    </p>
    <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
            var table = new Table.Book();
            var p = new {
                request.Name, request.Author, request.Isbn, LibraryId = int.Parse(request.LibraryId)
            };
            (isNew
                ? NonQuery.New(new {table, p}, "@@p.Insert(table)")
                : NonQuery.New(new {table, p, key = new {BookId = int.Parse(request.BookId)}}, "@@p.Update(key, table)")
                ).Execute();
        ]]>
    </script>
    <p>
        Если в базе данных добавить колонку в таблицу <code>Book</code>,
        то тест <code>CheckAllQueries</code> проинформирует о том,
        что в переменную <code>p</code> нужно добавить значение для новой колонки.
    </p>
    <p>
        Выполнение <code>DELETE</code>.
    </p>
    <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
NonQuery.New(new {BookId = int.Parse(bookId)}, "@@p.Delete(\"Book\")").Execute();
        ]]>
    </script>
    <h2>12. Решение проблемы добавления поля</h2>
    <p>
        Во многих корпоративных приложениях используется реляционная модель данных.
        Реляционная модель имеет крайне простую структуру.
        Если использовать неформальные термины, то реляционная модель это набор таблиц.
        Таблица состоит из набора колонок.
        Как постепенно построить или вырастить реляционную модель?
        Какие элементарные операции для этого требуются?
        Создание и удаление таблиц и колонок.
        Таким образом, добавление (и удаление) колонки является базовой 
        (а значит крайне важной операцией) для построения реляционной модели и, тем самым,
        всей информационной системы.
        Удивительно, но часто добавление поля требует от разработчика
        значительных усилий. Вот, что об этом пишет 
        <a href="http://martinfowler.com/books/eaa.html">Фаулер</a>:
        "Схема расслоения обладает и определенными недостатками. 
        Слои способны удачно инкапсулировать многое, но не все: модификация одного слоя подчас
        связана с необходимостью внесения каскадных изменений в остальные слои. Классический пример
        из области корпоративных программных приложений: поле, добавленное в таблицу базы данных,
        подлежит воспроизведению в графическом интерфейсе и должно найти соответствующее отображение
        в каждом промежуточном слое."
    </p>
    <p>
        В описанной выше схеме проблема синхронизации слоев при добавлении поля решена.
        Решение базируется на сообщениях от компилятора, от теста <code>CheckAllQueries</code>
        и на использовании метода <code>FindUsages</code>.
    </p>
    <p>
        Например, пусть на пользовательском интерфейсе отображается read only список
        постов. Поля списка связываются со свойствами интерфейса <code>IPostInfo</code>.
        Добавляем поле к списку, добавляем свойство к интерфейсу <code>IPostInfo</code>.
        <code>CheckAllQueries</code> сообщит нам о отм, что требуется добавить поле в 
        <code>SELECT</code>
        выражение, а затем в таблицу базы данных. <code>CheckAllQueries</code>
        сообщит нам, что нужно добавить параметры там, где вызываются
        <code>INSERT</code> и <code>UPDATE</code>. Компилятор нас приведет
        к форме, где заполяются поля, и мы созданим новое поле на форме ввода.
    </p>
    <h2>13. Razor на чистом ADO.NET</h2>
    <script type="syntaxhighlighter" class="brush: csharp">
        <![CDATA[
        private static void Example1(DateTime? date)
        {
            var postInfos = new {date}.Apply((с, p) => {
                c.CommandText = p.QFragment(@@"
SELECT  Id, CreationDate
FROM    Post
WHERE   1 = 1
@@if (date.HasValue) {
        @@:AND CreationDate >= @@date
}").ToString(c);
                return default(IPostInfo);
            }).List();
        }
        ]]>
    </script>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <script type="text/javascript">
        SyntaxHighlighter.all()
    </script>
    </body>
</html>